/**
 * 
 */
package truerefactor.metrics;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import truerefactor.graph.Accessibility;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.DiGraph;
import truerefactor.graph.MethodNode;

/**
 * WMC - An implementation of the Weighted Methods per Class metric, which
 * utilizes McCabe's Cyclomatic complexity as the complexity measurement.
 * 
 * @author Isaac Griffith
 */
public class WMC extends Metric {

    /**
     * Creates a new instance of the WMC metric
     */
    public WMC()
    {
        this.shortName = "WMC";
        this.name = "Weighted Methods Per Class";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see truerefactor.metrics.Metric#measure()
     */
    @Override
    public double measure(CodeGraph graph)
    {
        // WMC = the summation of the normalized (to nominal complexity of 1.0)
        // complexities of each
        // class.
        // Implemented using cyclomatic complexity

        List<Double> cList = new ArrayList<Double>();
        List<ClassNode> classes = graph.getClasses();

        double cc = 0;
        double tc = 0;

        // FIXME: include ability to count methods of superclasses up the
        // inheritance chain

        for (ClassNode cNode : classes)
        {
            List<CodeNode> methods = cNode.getMethods();
            List<Double> comp = new ArrayList<Double>();

            cc = 0;

            for (CodeNode node : methods)
            {
                MethodNode method = (MethodNode) node;

                int c = calcComplexity(method);
                comp.add((double) c);
            }

            double median = 0;
            if (!comp.isEmpty())
            {
                Collections.sort(comp);
                if (comp.size() % 2 == 0 && comp.size() >= 2)
                {
                    median = (comp.get((int) comp.size() / 2) + comp.get(((int) comp.size() / 2) - 1)) / 2;
                }
                else
                {
                    median = (comp.get((int) Math.ceil(comp.size() / 2)));
                }
            }

            if ((0.0 - median) < 0.0001)
                median = 1.0;

            for (int i = 0; i < comp.size(); i++)
            {
                double curr = comp.get(i);
                cc += curr / median;
            }

            cList.add(cc);
        }

        for (double c : cList)
        {
            tc += c;
        }

        return tc / classes.size();
    }

    /**
     * Measures WMC for a given ClassNode
     * 
     * @param cNode
     *            class node to measure
     * @return WMC for provided class node
     */
    public static int measure(ClassNode cNode)
    {
        List<MethodNode> methods = new ArrayList<MethodNode>();
        getAllMethods(methods, cNode);

        int cc = 0;

        for (MethodNode method : methods)
        {
            int c = calcComplexity(method);
            cc += c;
        }

        return cc;
    }

    /**
     * Measures the complexity of the provided MethodNode utilizing McCabe's
     * Cyclomatic Complexity
     * 
     * @param mNode
     *            method node to measure
     * @return complexity of the provided methodnode
     */
    public static int calcComplexity(MethodNode mNode)
    {
        // Cyclomatic complexity: complexity
        // where: complexity = edges - nodes + 2 * components
        // edges: Number of edges in the graph (subgraph of the method)
        // nodes: Number of Nodes in the graph (subgraph of the method)
        // components: Number of connected components (since this is a method P
        // is equal to 1)

        int complexity = 0;
        int edges = 0;
        int nodes = 0;
        int components = 1;

        if (mNode != null)
        {
            DiGraph subGraph = mNode.getStatementGraph();

            edges = subGraph.numberOfEdges();
            nodes = subGraph.numberOfNodes();

            complexity = edges - nodes + 2 * components;
        }

        return complexity;
    }

    /**
     * Returns all known methods implemented by this class including those that
     * it inherits
     * 
     * @param nodes
     *            list of method nodes to be filled by this method
     * @param start
     *            class node to determine all methods for
     */
    private static void getAllMethods(List<MethodNode> nodes, ClassNode start)
    {
        List<ClassNode> superClasses = start.getParentClasses();

        ClassNode superClass = null;
        for (CodeNode node : superClasses)
        {
            if (node instanceof ClassNode)
            {
                ClassNode cNode = (ClassNode) node;
                if (!cNode.isInterface())
                {
                    superClass = cNode;
                    break;
                }
            }
        }

        if (superClass == null)
            return;
        else
        {
            List<CodeNode> methods = superClass.getMethods();
            for (CodeNode node : methods)
            {
                if (node instanceof MethodNode)
                {
                    MethodNode method = (MethodNode) node;

                    if (!method.isAbstract() && !method.getAccessibility().equals(Accessibility.Private))
                    {
                        nodes.add(method);
                    }
                }
            }
        }

        getAllMethods(nodes, superClass);
    }
}
